using System.Collections.Generic;
using System.IO;
using System.Text;

namespace ScrewTurnHelper
{
	public class CsvParser
	{
		public static DataTable Parse(string data)
		{
			TextReader stream = new StringReader(data);

			var table = new DataTable();
			var csv = new CsvStream(stream);
			string[] row = csv.GetNextRow();
			if (row == null)
				return null;
			while (row != null)
			{
				table.Rows.Add(row);
				row = csv.GetNextRow();
			}
			return table;
		}

		private class CsvStream
		{
			private readonly TextReader stream;

			public CsvStream(TextReader s)
			{
				stream = s;
			}

			public string[] GetNextRow()
			{
				var row = new List<string>();
				while (true)
				{
					string item = GetNextItem();
					if (item == null)
						return row.Count == 0 ? null : row.ToArray();
					row.Add(item);
				}
			}

			private bool EOS = false;
			private bool EOL = false;

			private string GetNextItem()
			{
				if (EOL)
				{
					// previous item was last in line, start new line
					EOL = false;
					return null;
				}

				bool quoted = false;
				bool predata = true;
				bool postdata = false;
				var item = new StringBuilder();

				while (true)
				{
					char c = GetNextChar(true);
					if (EOS)
						return item.Length > 0 ? item.ToString() : null;

					if ((postdata || !quoted) && c == ',')
						// end of item, return
						return item.ToString();

					if ((predata || postdata || !quoted) && (c == '\x0A' || c == '\x0D'))
					{
						// we are at the end of the line, eat newline characters and exit
						EOL = true;
						if (c == '\x0D' && GetNextChar(false) == '\x0A')
							// new line sequence is 0D0A
							GetNextChar(true);
						return item.ToString();
					}

					if (predata && c == ' ')
						// whitespace preceeding data, discard
						continue;

					if (predata && c == '"')
					{
						// quoted data is starting
						quoted = true;
						predata = false;
						continue;
					}

					if (predata)
					{
						// data is starting without quotes
						predata = false;
						item.Append(c);
						continue;
					}

					if (c == '"' && quoted)
					{
						if (GetNextChar(false) == '"')
							// double quotes within quoted string means add a quote       
							item.Append(GetNextChar(true));
						else
							// end-quote reached
							postdata = true;
						continue;
					}

					// all cases covered, character must be data
					item.Append(c);
				}
			}

			private readonly char[] buffer = new char[4096];
			private int pos = 0;
			private int length = 0;

			private char GetNextChar(bool eat)
			{
				if (pos >= length)
				{
					length = stream.ReadBlock(buffer, 0, buffer.Length);
					if (length == 0)
					{
						EOS = true;
						return '\0';
					}
					pos = 0;
				}
				if (eat)
					return buffer[pos++];
				return buffer[pos];
			}
		}
	}
}